# 魔兽世界暗牧按键精灵实现
# pyuic5 -o wowui.py wow.ui
# from re import S

import sys, time, os, hashlib, threading, win32con, socket, psutil, binascii
import cv2

import win32gui as gui
import win32ui as ui
import win32api as api
import numpy as np
import key

# 图片比较
from skimage.measure import compare_ssim

# 当前界面类
from wowui import Ui_MainWindow as wowUI

from PyQt5.QtWidgets import QApplication, QAction, QMainWindow, QTableWidgetItem
from PyQt5.QtGui import QIcon, QPixmap, QImage,QCloseEvent
from PyQt5.QtCore import pyqtSignal, QSettings, Qt

# from pyhooked import Hook, KeyboardEvent
from pynput.keyboard import Controller, Key, Listener


class MainWindow(QMainWindow, wowUI):
    # 获取按键监测
    keyGet = pyqtSignal()
    keyPressSingal = pyqtSignal([str])
    keyReleaseSingal = pyqtSignal([str])

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.keyPress = "nan"
        self.kb=Controller()

        self.initUI()

        self.keyboradThread = threading.Thread(target=self.start_listen)
        self.keyboradThread.start()

    def initUI(self):
        self.setupUi(self)

        self.keyPressList = []

        self.lineEdit_8.setText("0")
        self.lineEdit_9.setText("0")
        self.lineEdit_12.setText("0")
        self.lineEdit_13.setText("0")
        self.lineEdit_15.setText("0")
        self.lineEdit_16.setText("0")
        self.lineEdit_10.setText("0")
        self.lineEdit_11.setText("0")
        # 转置字典键值list
        self.keyIdx = list(key.KEY_CODE.keys())

        # 注册码
        self.nMd5 = hashlib.md5()
        self.hasiMD5()
        # 相似度滑动模块设置
        self.doubleSpinBox.setRange(0.0, 1.0)
        self.doubleSpinBox.setSingleStep(0.1)
        # 按键comboBox中添加键值
        self.comboBox.addItems(self.keyIdx)
        self.comboBox_2.addItems(self.keyIdx)
        self.comboBox_3.addItems(self.keyIdx)
        self.comboBox_4.addItems(self.keyIdx)
        # 获取ini文件中的功能
        self.setFun()
        # 获取技能位图和按键信息
        self.getInitImg()

        self.homeIcon = QAction(QIcon("./image/home-4-line.png"), "home", self)
        self.toolBar.addAction(self.homeIcon)

        self.setIcon = QAction(QIcon("./image/settings-4-line.png"), "set", self)
        self.toolBar.addAction(self.setIcon)

        self.saveIcon = QAction(QIcon("./image/save-line.png"), "save", self)
        self.toolBar.addAction(self.saveIcon)

        self.playIcon = QAction(QIcon("./image/play-line.png"), "play", self)
        self.toolBar.addAction(self.playIcon)

        self.stopIcon = QAction(QIcon("./image/stop-line.png"), "stop", self)
        self.toolBar.addAction(self.stopIcon)

        self.exitIcon = QAction(QIcon("./image/login-box-line.png"), "exit", self)
        self.toolBar.addAction(self.exitIcon)
        # 设置起始界面
        self.stackedWidget.setCurrentIndex(0)
        self.tabWidget.setCurrentIndex(0)
        # 设置截图界面的输入
        self.lineEdit_3.setText(str(0))
        self.lineEdit_4.setText(str(0))
        self.lineEdit_5.setText(str(api.GetSystemMetrics(win32con.SM_CYSCREEN)))
        self.lineEdit_6.setText(str(api.GetSystemMetrics(win32con.SM_CXSCREEN)))
        # 创建截图类内截图坐标值
        self.screenX = int(self.lineEdit_3.text())
        self.screenY = int(self.lineEdit_4.text())
        self.screenH = int(self.lineEdit_5.text())
        self.screenW = int(self.lineEdit_6.text())

        self.homeIcon.triggered.connect(self.homeIconFun)
        self.setIcon.triggered.connect(self.setIconFun)
        self.exitIcon.triggered.connect(self.exitIconFun)
        self.playIcon.triggered.connect(self.playIconFun)
        self.stopIcon.triggered.connect(self.stopIconFun)
        # 针对set界面的Tab进行了一个函数设计
        self.tabWidget.currentChanged.connect(self.set2Fun)
        # set界面第二tab查看按钮的信号槽
        self.pushButton.clicked.connect(self.screenFun)
        # 按键信号触发，连接按键重置
        self.keyGet.connect(self.keyReset)

        self.keyPressSingal.connect(self.keyPressSet)
        self.keyReleaseSingal.connect(self.keyReleaseSet)
        # 保存按键信号触发
        self.saveIcon.triggered.connect(self.saveINI)
        self.psIconInit()
        return

    def setFun(self):
        # 判定是否创建config文件
        if not os.path.exists("config.ini"):
            # 创建config文件
            os.system(r"touch {}".format("config.ini"))
            # 关联config文件
            self.settings = QSettings("config.ini", QSettings.IniFormat)
            # 注册码初始化
            self.lineEdit_2.setText("0")
            # 设定注册码值
            self.settings.setValue("SETUP/REGISTRATION", self.lineEdit_2.text())
            # 创建图片list
            self.settings.setValue("IMAGE/FIRST", str("BEGIN"))
            # 启动读取IMAGE中内容
            self.settings.beginGroup("IMAGE")
            # 读取IMAGE中的键值
            keys = self.settings.childKeys()
            # 创建读取列表
            lst = []
            for i in keys:
                # 放入键值
                lst.append(i)
                # 放入数据
                lst.append(self.settings.value(i))
            # 将数据转置
            self.imgPath = np.array(lst).reshape(int(len(lst) / 2), 2)
        else:
            # 关联config文件
            self.settings = QSettings("config.ini", QSettings.IniFormat)
            # 读取注册码值
            self.lineEdit_2.setText(self.settings.value("SETUP/REGISTRATION"))
            # 启动读取IMAGE中内容
            self.settings.beginGroup("IMAGE")
            # 读取IMAGE中的键值
            keys = self.settings.childKeys()
            # 创建读取列表
            lst = []
            for i in keys:
                # 放入键值
                lst.append(i)
                # 放入数据
                lst.append(self.settings.value(i))
            # 将数据转置
            self.imgPath = np.array(lst).reshape(int(len(lst) / 2), 2)
            self.settings.endGroup()
            for i in range(len(self.imgPath)):
                if self.imgPath[i][0] != "FIRST":
                    self.tableWidget.setItem(i, 0, QTableWidgetItem(self.imgPath[i][0]))
                    self.tableWidget.setItem(i, 1, QTableWidgetItem(self.imgPath[i][1]))
            # 启动读取REALFUN功能中的内容
            self.settings.beginGroup("REALFUN")
            # 获取REALFUN中的键值
            keys = self.settings.childKeys()
            # 设置switch-case字典，以键为case，键值为dat，输入lambda中def
            swRealFun = {
                "FUN1": lambda dat: self.checkBox_3.setChecked(True) if dat == "true" else self.checkBox_3.setChecked(False),
                "FUN1-X": lambda dat: self.lineEdit_8.setText(dat),
                "FUN1-Y": lambda dat: self.lineEdit_9.setText(dat),
                "FUN1-KEY": lambda dat: self.comboBox_2.setCurrentText(dat),
                "FUN2": lambda dat: self.checkBox_4.setChecked(True) if dat == "true" else self.checkBox_4.setChecked(False),
                "FUN2-X": lambda dat: self.lineEdit_12.setText(dat),
                "FUN2-Y": lambda dat: self.lineEdit_13.setText(dat),
                "FUN2-KEY": lambda dat: self.comboBox_3.setCurrentText(dat),
                "FUN3": lambda dat: self.checkBox_5.setChecked(True) if dat == "true" else self.checkBox_5.setChecked(False),
                "FUN3-X": lambda dat: self.lineEdit_15.setText(dat),
                "FUN3-Y": lambda dat: self.lineEdit_16.setText(dat),
                "FUN3-KEY": lambda dat: self.comboBox_4.setCurrentText(dat),
                "FUN-W": lambda dat: self.lineEdit_10.setText(dat),
                "FUN-H": lambda dat: self.lineEdit_11.setText(dat),
                "FUN-similar": lambda dat: self.doubleSpinBox.setValue(float(dat)),
                "FUN-KeepPress": lambda dat: self.checkBox.setChecked(True) if dat == "true" else self.checkBox.setChecked(False),
                "FUN-ShortPress": lambda dat: self.checkBox_2.setChecked(True) if dat == "true" else self.checkBox_2.setChecked(False),
                "FUN-STOP": lambda dat: self.comboBox.setCurrentText(dat),
            }
            # 遍历REALFUN中的键
            for i in keys:
                swRealFun[i](self.settings.value(i))
            # 关闭REALFUN
            self.settings.endGroup()

    # 数据保存
    def saveINI(self):
        # 对settings类设定数值，保存当前注册表内容
        self.settings.setValue("SETUP/REGISTRATION", self.lineEdit_2.text())
        # 获取tableWeight的行列数量
        rCnt = self.tableWidget.rowCount()
        cCnt = self.tableWidget.columnCount()
        # 创建一个空的列表
        lst = []
        # 遍历整个表格
        for i in range(rCnt):
            for j in range(cCnt):
                item = self.tableWidget.item(i, j)
                # 如果数据不为空
                if item != None:
                    # 将数据放入列表
                    lst.append(item.text())
        # 将数据转置成numpy，形成两列的数据
        if len(lst) % 2 != 0:
            lst.pop()
        # 将列表转置成numpy
        self.imgPath = np.array(lst).reshape(int(len(lst) / 2), 2)
        # 开启settings中的IMAGE中的键
        self.settings.beginGroup("IMAGE")
        # 清空IMAGE中的键值
        self.settings.remove("")
        # 将self.imgPath中的数据存入settings中
        for i in self.imgPath:
            self.settings.setValue(i[0], str(i[1]))
        # 关闭IMAGE的组
        self.settings.endGroup()
        # 开启settings中的REALFUN中的键
        self.settings.beginGroup("REALFUN")
        # 清空REALFUN中的键值
        self.settings.remove("")
        # 功能1状态保存
        self.settings.setValue("FUN1", self.checkBox_3.isChecked())
        self.settings.setValue("FUN1-X", self.lineEdit_8.text())
        self.settings.setValue("FUN1-Y", self.lineEdit_9.text())
        self.settings.setValue("FUN1-KEY", self.comboBox_2.currentText())
        # 功能2状态保存
        self.settings.setValue("FUN2", self.checkBox_4.isChecked())
        self.settings.setValue("FUN2-X", self.lineEdit_12.text())
        self.settings.setValue("FUN2-Y", self.lineEdit_13.text())
        self.settings.setValue("FUN2-KEY", self.comboBox_3.currentText())
        # 功能3状态保存
        self.settings.setValue("FUN3", self.checkBox_5.isChecked())
        self.settings.setValue("FUN3-X", self.lineEdit_15.text())
        self.settings.setValue("FUN3-Y", self.lineEdit_16.text())
        self.settings.setValue("FUN3-KEY", self.comboBox_4.currentText())
        # 基础功能保存
        self.settings.setValue("FUN-W", self.lineEdit_10.text())
        self.settings.setValue("FUN-H", self.lineEdit_11.text())
        self.settings.setValue("FUN-similar", self.doubleSpinBox.text())
        # 长按功能获取
        self.settings.setValue("FUN-KeepPress", self.checkBox.isChecked())
        # 短按功能获取
        self.settings.setValue("FUN-ShortPress", self.checkBox_2.isChecked())
        self.settings.setValue("FUN-STOP", self.comboBox.currentText())

        # 关闭REALFUN的组
        self.settings.endGroup()

    def playIconFun(self):
        s = self.lineEdit_2.text()
        if self.nMd5Result == s:
            self.playIcon.setChecked(True)
            self.stopIcon.setChecked(False)
            self.t = threading.Thread(target=self.commFun)
            self.t.start()
        else:
            self.playIcon.setChecked(False)
            self.stopIcon.setChecked(True)

    def stopIconFun(self):
        self.playIcon.setChecked(False)
        self.stopIcon.setChecked(True)

    def psIconInit(self):
        self.stopIcon.setDisabled(False)
        self.playIcon.setDisabled(False)
        self.stopIcon.setCheckable(True)
        self.playIcon.setCheckable(True)
        self.stopIcon.setChecked(True)
        self.playIcon.setChecked(False)

    def homeIconFun(self):
        self.stackedWidget.setCurrentIndex(0)
        self.stopIcon.setIcon(QIcon("./image/stop-line.png"))
        self.playIcon.setIcon(QIcon("./image/play-line.png"))
        self.psIconInit()

    def setIconFun(self):
        self.stackedWidget.setCurrentIndex(1)
        self.stopIcon.setIcon(QIcon("./image/stop-line-gray.png"))
        self.playIcon.setIcon(QIcon("./image/play-line-gray.png"))
        self.stopIcon.setDisabled(True)
        self.playIcon.setDisabled(True)
        self.stopIcon.setCheckable(False)
        self.playIcon.setCheckable(False)
        self.set2Fun()

    def exitIconFun(self):
        QApplication.instance().quit()
        sys.exit(0)

    # 获取初始化图像信息
    def getInitImg(self):
        # 创建按键数值列表
        self.list_key = []
        # 创建图像存储地址列表
        list_jpg = []
        # 遍历set中所读取的img信息
        for i in self.imgPath:
            # 判定首地址
            if i[0] != "FIRST":
                # 将图像地址导入
                list_jpg.append("./jpgDat/" + i[0] + ".jpg")
                # 将按键数值导入
                self.list_key.append(i[1])
        # 创建灰度图列表
        self.list_gray = []
        # 遍历图像地址列表
        for i in list_jpg:
            # 读取图像
            imp = cv2.imread(i, cv2.IMREAD_COLOR)
            # 将图像转成灰度图
            gray = cv2.cvtColor(imp, cv2.COLOR_BGR2GRAY)
            # 将灰度图放入灰度图列表
            self.list_gray.append(gray)

    # 获取当前图像
    def getImg(self, x, y, w, h):
        # time.sleep(0.1)
        # 魔兽世界有句柄保护，所以使用hwnd编码是无法进行句柄截图
        hwnd = 0
        # 根据窗口句柄获取窗口的设备上下文DC（Divice Context）
        hwndDC = gui.GetWindowDC(hwnd)
        # 根据窗口的DC获取mfcDC
        mfcDC = ui.CreateDCFromHandle(hwndDC)
        # mfcDC创建可兼容的DC
        saveDC = mfcDC.CreateCompatibleDC()
        # 创建bigmap准备保存图片
        saveBitMap = ui.CreateBitmap()
        # 获取监控器信息
        MoniterDev = api.EnumDisplayMonitors(None, None)
        # 屏幕截图位置，x、y为坐标点，w、h为宽度和高度

        # 为bitmap开辟空间
        saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
        # 高度saveDC，将截图保存到saveBitmap中
        saveDC.SelectObject(saveBitMap)
        # 截取从左上角（0，0）长宽为（w，h）的图片
        saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY)
        # 对截取的saveBitMap进行数据格式转换，先转成bit类型，然后在进行numpy转换
        signedIntsArray = saveBitMap.GetBitmapBits(True)
        img = np.frombuffer(signedIntsArray, dtype="uint8")
        # 根据像素格式进行斜度处理
        img.shape = (h, w, 4)
        # gui.DeleteObject(saveBitMap)
        saveDC.DeleteDC()
        # 析构数据，放置内存泄漏
        gui.DeleteObject(saveBitMap.GetHandle())
        mfcDC.DeleteDC()
        gui.ReleaseDC(hwnd, hwndDC)
        # del saveBitMap
        del hwndDC
        del mfcDC
        del saveDC
        img = img[:, :, [2, 1, 0]]
        # 返回数据灰度图进行后续图片比较判定
        # return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    def getIndex(self, img, list_gray):
        count = 0
        img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        for i in list_gray:
            img_gray = cv2.resize(img_gray, (i.shape[1], i.shape[0]), interpolation=cv2.INTER_AREA)
            (score, diff) = compare_ssim(img_gray, i, full=True, multichannel=True)
            if score > 0.5:
                return count
                break
            else:
                count += 1

        return -1

    def key_press(self, key):
        self.kb.press(key)
        time.sleep(0.02)
        self.kb.release(key)

    def commFun(self):
        fun1_cs = self.checkBox_3.isChecked()
        fun2_cs = self.checkBox_4.isChecked()
        fun3_cs = self.checkBox_5.isChecked()
        w = int(self.lineEdit_10.text())
        h = int(self.lineEdit_11.text())
        fun1_x = int(self.lineEdit_8.text())
        fun1_y = int(self.lineEdit_9.text())
        fun2_x = int(self.lineEdit_12.text())
        fun2_y = int(self.lineEdit_13.text())
        fun3_x = int(self.lineEdit_15.text())
        fun3_y = int(self.lineEdit_16.text())
        fun1_key = self.comboBox_2.currentText()
        fun2_key = self.comboBox_3.currentText()
        fun3_key = self.comboBox_4.currentText()
        fun_stop = self.comboBox.currentText()
        fun_keyPress = self.checkBox_2.isChecked()  # 长按为true 短按为false
        fun1_begin = False
        fun2_begin = False
        fun3_begin = False
        while self.playIcon.isChecked():
            time.sleep(0.05)
            if fun_keyPress:
                if self.keyPress == fun1_key and fun1_cs:
                    fun1_begin = True
                    fun2_begin = False
                    fun3_begin = False
                    self.keyGet.emit()
                elif self.keyPress == fun2_key and fun2_cs:
                    fun1_begin = False
                    fun2_begin = True
                    fun3_begin = False
                    self.keyGet.emit()
                elif self.keyPress == fun3_key and fun3_cs:
                    fun1_begin = False
                    fun2_begin = False
                    fun3_begin = True
                    self.keyGet.emit()
                elif self.keyPress == fun_stop:
                    fun1_begin = False
                    fun2_begin = False
                    fun3_begin = False
                    self.keyGet.emit()
            else:
                if self.keyPressList.count(fun1_key) > 0 and fun1_cs:
                    self.corefun(fun1_x, fun1_y, w, h)
                    self.keyGet.emit()
                elif self.keyPressList.count(fun2_key) > 0 and fun2_cs:
                    self.corefun(fun2_x, fun2_y, w, h)
                    self.keyGet.emit()
                elif self.keyPressList.count(fun3_key) > 0 and fun3_cs:
                    self.corefun(fun3_x, fun3_y, w, h)
                    self.keyGet.emit()
                elif self.keyPressList.count(fun_stop) > 0:
                    self.keyGet.emit()

            if fun1_begin:
                self.corefun(fun1_x, fun1_y, w, h)
            elif fun2_begin:
                self.corefun(fun2_x, fun2_y, w, h)
            elif fun3_begin:
                self.corefun(fun3_x, fun3_y, w, h)

    def corefun(self, x, y, w, h):
        idx = self.getIndex(self.getImg(x, y, w, h), self.list_gray)
        if idx == -1:
            return
        else:
            self.key_press(self.list_key[idx])

    def hasiMD5(self):
        cpu_use = psutil.cpu_count()
        s = socket.gethostname() + str(cpu_use)
        self.lineEdit.setText(str(binascii.b2a_hex(s.encode("utf-8"))))
        self.nMd5.update(s.encode("utf-8"))
        self.nMd5Result = self.nMd5.hexdigest()

    # set界面第二个tab，启动一个实时截屏的线程，刷新label
    def set2Fun(self):
        # 判断set-tab当前选中是否为第二个
        if self.tabWidget.currentIndex() == 1:
            # 设定线程
            self.t_set2 = threading.Thread(target=self.imgSet2)
            self.t_set2.start()

    # set界面第二个tab，label设置图片截取坐标
    def imgSet2(self):
        # 判断set按键是否被选中，判断set-tab是否为第二个
        while self.stackedWidget.currentIndex() == 1 and self.tabWidget.currentIndex() == 1:
            # 根据坐标获取图像
            img = self.getImg(self.screenX, self.screenY, self.screenW, self.screenH)
            # cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            # 图像转置，根据颜色进行RGB变换
            img_copy = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
            pix = QImage(img_copy.data, img_copy.shape[1], img_copy.shape[0], QImage.Format_RGB32)
            # 当监测到按下F10，进行截图并保存
            if self.keyPress == "f10":
                # cv2.imwrite("get.png",img,[int(cv2.IMWRITE_PNG_COMPRESSION),95])
                cv2.imwrite(self.lineEdit_7.text() + ".jpg", img)
                # 触发按键获取信号
                self.keyGet.emit()
            # 获取label的高度和宽度
            h_label = self.label_5.height()
            w_label = self.label_5.width()
            # 根据label尺寸进行图像设置，不影响图像失真
            pixmap = QPixmap(pix.scaled(w_label, h_label, Qt.KeepAspectRatio))
            # 将label中动态设置图像
            self.label_5.setPixmap(pixmap)

    # 获取set界面第二tab的lineEdit值
    def screenFun(self):
        self.screenX = int(self.lineEdit_3.text())
        self.screenY = int(self.lineEdit_4.text())
        self.screenH = int(self.lineEdit_5.text())
        self.screenW = int(self.lineEdit_6.text())

    # 按键键值重置
    def keyPressSet(self,dat):
        self.keyPress = dat
        if self.keyPressList.count(dat) == 0:
            self.keyPressList.append(dat)

    def keyReleaseSet(self,dat):
        if self.keyPressList.count(dat):
            self.keyPressList.remove(dat)
    
    def keyReset(self):
        self.keyPress = "nan"

    def on_press(self, key):
        try:
            # print("正在按压:", format(key.char))
            self.keyPressSingal.emit(key.char)
        except AttributeError:
            # print("正在按压:", format(key))
            self.keyPressSingal.emit(key.name)

    # 监听释放
    def on_release(self, key):
        try:
            # print("已经释放:", format(key.char))
            self.keyReleaseSingal.emit(key.char)
        except AttributeError:
            # print("已经释放:", format(key))
            self.keyReleaseSingal.emit(key.name)

    def start_listen(self):
        with Listener(on_press=self.on_press, on_release=self.on_release) as self.listener:
            self.listener.join()

    def closeEvent(self, a0: QCloseEvent) -> None:
        self.listener.stop()
        os._exit(0)
        return super().closeEvent(a0)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

